package com.sojson.config.qps.sentinel.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.annotation.aspectj.AbstractSentinelAspectSupport;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.sojson.annotation.aspect.PointcutsClient;
import com.sojson.config.qps.sentinel.exception.CustomUrlBlockHandler;
import com.sojson.config.qps.sentinel.util.SentinelUtil;

/**
 * 自定义限流切面(拦截全部资源)
 * 
 * @author liu
 * @date 2021-08-25
 */
@Aspect
@Component
public class CustomSentinelAspectSupport extends AbstractSentinelAspectSupport {

    /**
     * 默认拦截后处理的注解
     */
    private SentinelResource SentinelResource = new SentinelResource() {

        @SuppressWarnings("synthetic-access")
        @Override
        public Class<? extends Annotation> annotationType() {
            return SentinelResource.getClass();
        }

        @Override
        public String value() {
            return "all";
        }

        @Override
        public Class<?>[] fallbackClass() {
            return new Class[] {CustomUrlBlockHandler.class};
        }

        @Override
        public String fallback() {
            return "";
        }

        @SuppressWarnings("unchecked")
        @Override
        public Class<? extends Throwable>[] exceptionsToTrace() {
            return new Class[] {Throwable.class};
        }

        @SuppressWarnings("unchecked")
        @Override
        public Class<? extends Throwable>[] exceptionsToIgnore() {
            return new Class[] {};
        }

        @Override
        public EntryType entryType() {
            return EntryType.OUT;
        }

        @Override
        public String defaultFallback() {
            return "";
        }

        @Override
        public Class<?>[] blockHandlerClass() {
            return new Class[] {};
        }

        @Override
        public String blockHandler() {
            return "";
        }

        @Override
        public int resourceType() {
            // TODO Auto-generated method stub
            return 0;
        }
    };

    /** 切点 */
    @Pointcut(PointcutsClient.POINT_CONTROLLER)
    public void sentinelResourceAnnotationPointcut() {}

    /** 环绕通知 */
    @SentinelResource
    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);

        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
            // Should not go through here.
            // TODO
            // throw new IllegalStateException("Wrong state for SentinelResource annotation");
            annotation = SentinelResource;
        }
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
            Object result = pjp.proceed();
            return result;
        } catch (BlockException e) {
            // TODO
            // return handleBlockException(pjp, annotation, e);
            SentinelUtil.throwException(e);
            return null;
        } catch (Throwable ex) {
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex, annotation);
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }

}